from flask import Flask, render_template, request, jsonify, session, redirect, url_for, send_file, Response
from typing import Any, Callable, Optional, TypedDict
import os
import uuid
from werkzeug.utils import secure_filename
from werkzeug.security import generate_password_hash, check_password_hash
import requests
from bs4 import BeautifulSoup
import re
from datetime import datetime
import json
import sqlite3
from functools import wraps
import config
from annotation_replies_api import annotation_replies_bp

# 尝试导入jieba，如果不存在则使用简单的关键词提取
try:
    import jieba.analyse
    USE_JIEBA = True
except ImportError:
    USE_JIEBA = False

# 尝试导入markdown，如果不存在则提供替代方案
try:
    import markdown
    from markdown.extensions import codehilite, toc, tables
    USE_MARKDOWN = True
except ImportError:
    USE_MARKDOWN = False
    
from PIL import Image
# 尝试导入easyocr，如果不存在则提供替代方案
try:
    import easyocr
    USE_EASYOCR = True
    # 初始化EasyOCR读取器（支持中英文）
    # 注意：延迟初始化，避免在导入时就加载模型
    ocr_reader = None
except ImportError:
    USE_EASYOCR = False
    ocr_reader = None

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = config.UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = config.MAX_CONTENT_LENGTH  # 16MB max upload
app.config['SECRET_KEY'] = config.SECRET_KEY

# 注册批注回复蓝图
app.register_blueprint(annotation_replies_bp)

# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

# 导入数据库迁移模块
from db_migration import init_database, get_db_connection, DATABASE

# 确保数据目录存在
os.makedirs('data', exist_ok=True)


def login_required(f: Callable) -> Callable:
    """登录装饰器"""
    @wraps(f)
    def decorated_function(*args: Any, **kwargs: Any) -> Any:
        if 'user_id' not in session:
            return jsonify({'error': '请先登录'}), 401
        return f(*args, **kwargs)
    return decorated_function

# 提取文本关键词
def extract_keywords(text: str, top_n: int = 5) -> list[str]:
    if not text:
        return []
    
    if USE_JIEBA:
        return jieba.analyse.extract_tags(text, topK=top_n)
    else:
        # 简单的关键词提取方法（按词频）
        words = re.findall(r'\b\w+\b', text.lower())
        word_count: dict[str, int] = {}
        for word in words:
            if len(word) > 1:  # 忽略单个字符
                word_count[word] = word_count.get(word, 0) + 1
        
        # 按频率排序并返回前N个
        sorted_words = sorted(word_count.items(), key=lambda x: x[1], reverse=True)
        return [word for word, _ in sorted_words[:top_n]]

# 从图片中提取文本
def extract_text_from_image(image_path: str) -> str:
    try:
        if USE_EASYOCR:
            # 延迟初始化OCR读取器，仅在需要时加载模型
            global ocr_reader
            if ocr_reader is None:
                ocr_reader = easyocr.Reader(['ch_sim', 'en'])
            
            # 使用EasyOCR提取文本
            results = ocr_reader.readtext(image_path)
            # 提取所有识别到的文本
            text = ' '.join([result[1] for result in results])
            return text
        else:
            # 如果没有OCR库，返回提示信息
            return "需要安装easyocr库来提取图片文本"
    except Exception as e:
        print(f"图片文本提取错误: {e}")
        return ""

# HTML 安全净化
def sanitize_html(html: Optional[str]) -> str:
    if not html:
        return ""
    
    try:
        soup = BeautifulSoup(html, 'html.parser')
        allowed_tags: set[str] = {'p','pre','code','h1','h2','h3','h4','h5','h6','ul','ol','li','strong','em','blockquote','a','img','table','thead','tbody','tr','th','td','hr','br'}
        allowed_attrs: dict[str, list[str]] = {
            'a': ['href','title','target','rel'],
            'img': ['src','alt','title']
        }
        
        # 先移除危险标签
        for t in soup(['script','style','iframe','object','embed']):
            t.decompose()
        
        for tag in soup.find_all(True):
            if tag.name not in allowed_tags:
                tag.unwrap()
                continue
            
            # 清理属性
            attrs = dict(tag.attrs)
            for attr in list(attrs.keys()):
                # 移除事件属性
                if attr.startswith('on'):
                    if attr in tag.attrs:
                        del tag.attrs[attr]
                        continue
                
                if tag.name in allowed_attrs and attr in allowed_attrs[tag.name]:
                    # 限制 href/src 协议
                    if tag.name == 'a' and attr == 'href':
                        href = tag.attrs.get('href', '') or ''
                        if href.strip().lower().startswith('javascript:'):
                            del tag.attrs['href']
                        else:
                            tag.attrs['rel'] = 'noopener noreferrer'
                            tag.attrs['target'] = '_blank'
                    elif tag.name == 'img' and attr == 'src':
                        src = tag.attrs.get('src', '') or ''
                        if not (src.startswith('http://') or src.startswith('https://') or src.startswith('data:image')):
                            del tag.attrs['src']
                else:
                    if attr in tag.attrs:
                        del tag.attrs[attr]
        
        return str(soup)
    except Exception as e:
        print(f"HTML净化错误: {e}")
        return html if html else ""

def derive_title_from_text(text: Optional[str], max_len: int = 60) -> Optional[str]:
    """从纯文本内容中提取适合作为标题的第一行；若过长或不存在则返回None"""
    if not text:
        return None
    for line in text.splitlines():
        line = line.strip()
        if not line:
            continue
        # 直接使用首个非空行，若过长则放弃
        return line if len(line) <= max_len else None
    return None

# 为Markdown处理结果定义类型
class MarkdownResult(TypedDict):
    raw_content: str
    html_content: str
    title: str
    plain_text: str

# 处理Markdown文件
# 为PDF处理结果定义类型
class PdfResult(TypedDict):
    content: str
    title: str
    page_count: int

# 处理PDF文件
def process_pdf_file(file_path: str) -> PdfResult:
    try:
        import fitz  # PyMuPDF
        
        # 打开PDF文件
        doc = fitz.open(file_path)
        
        # 获取页数
        page_count = len(doc)
        
        # 提取文本内容
        text_content = ""
        for page_num in range(min(page_count, 50)):  # 限制最多处理50页
            page = doc[page_num]
            page_text = page.get_text()
            if page_text:
                text_content += page_text
                text_content += f"\n\n--- 第 {page_num + 1} 页 ---\n\n"
        
        # 提取元数据
        metadata = doc.metadata
        doc_title = metadata.get('title', '')
        
        # 如果元数据中没有标题，尝试从文件名或内容提取
        if not doc_title:
            # 从文件名提取
            doc_title = os.path.basename(file_path)
            doc_title = os.path.splitext(doc_title)[0]
            
            # 如果第一页有内容，尝试提取标题
            if text_content:
                first_line = text_content.strip().split('\n')[0].strip()
                if first_line and len(first_line) < 100:  # 合理的标题长度
                    doc_title = first_line
        
        # 提取目录（如果有）
        toc = doc.get_toc()
        toc_text = ""
        if toc:
            toc_text = "目录:\n"
            for level, title, page in toc:
                toc_text += f"{'  ' * (level-1)}- {title} (第{page}页)\n"
            toc_text += "\n"
        
        # 关闭文档
        doc.close()
        
        return {
            'content': toc_text + text_content if toc_text else text_content,
            'title': doc_title,
            'page_count': page_count
        }
    except ImportError:
        return {
            'content': "需要安装PyMuPDF库来处理PDF文件",
            'title': os.path.basename(file_path),
            'page_count': 0
        }
    except Exception as e:
        print(f"处理PDF文件错误: {e}")
        return {
            'content': f"处理PDF文件时出错: {str(e)}",
            'title': os.path.basename(file_path),
            'page_count': 0
        }

def process_markdown_file(file_path: str) -> MarkdownResult:
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            markdown_content = f.read()
        
        if USE_MARKDOWN:
            # 配置markdown扩展
            extensions = ['codehilite', 'toc', 'tables', 'fenced_code']
            
            # 转换markdown为HTML
            md = markdown.Markdown(extensions=extensions)
            html_content = md.convert(markdown_content)
            html_content = sanitize_html(html_content)
            
            # 提取标题（如果有的话）
            title = ""
            lines = markdown_content.split('\n')
            for line in lines:
                if line.startswith('# '):
                    title = line[2:].strip()
                    break
            
            return {
                'raw_content': markdown_content,
                'html_content': html_content,
                'title': title or "Markdown文档",
                'plain_text': markdown_content  # 用于搜索和关键词提取
            }
        else:
            # 如果没有markdown库，只返回原始内容
            return {
                'raw_content': markdown_content,
                'html_content': f'<pre>{markdown_content}</pre>',
                'title': "Markdown文档",
                'plain_text': markdown_content
            }
    except Exception as e:
        print(f"处理Markdown文件错误: {e}")
        return {
            'raw_content': "",
            'html_content': f"<p>处理Markdown文件时出错: {str(e)}</p>",
            'title': "错误",
            'plain_text': ""
        }

# 检查文件类型
def is_allowed_file(filename: str) -> bool:
    allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.md', '.markdown', '.txt', '.pdf'}
    return os.path.splitext(filename.lower())[1] in allowed_extensions

def get_file_type(filename: str) -> str:
    ext = os.path.splitext(filename.lower())[1]
    if ext in {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}:
        return 'image'
    elif ext in {'.md', '.markdown'}:
        return 'markdown'
    elif ext == '.txt':
        return 'text_file'
    elif ext == '.pdf':
        return 'pdf'
    else:
        return 'unknown'

# 为URL提取结果定义类型
class UrlExtractResult(TypedDict):
    title: str
    content: str
    url: str

# 从网页链接提取内容
def extract_from_url(url: str) -> UrlExtractResult:
    try:
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 提取标题
        title = soup.title.string if soup.title else ""
        title_str = str(title) if title is not None else ""
        
        # 提取正文内容
        # 移除脚本和样式元素
        for script in soup(["script", "style"]):
            script.extract()
            
        # 获取文本
        text = soup.get_text(separator=' ', strip=True)
        
        # 清理文本
        lines = (line.strip() for line in text.splitlines())
        chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
        text = '\n'.join(chunk for chunk in chunks if chunk)
        
        return {
            'title': title_str,
            'content': text[:1000],  # 限制内容长度
            'url': url
        }
    except Exception as e:
        print(f"URL提取错误: {e}")
        return {
            'title': url,
            'content': f"无法提取内容: {str(e)}",
            'url': url
        }

# 生成摘要
def generate_summary(text: Optional[str], max_length: int = 200) -> str:
    if not text:
        return ""
    
    # 简单摘要方法：取前N个字符
    if len(text) <= max_length:
        return text
    return text[:max_length] + "..."

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/register', methods=['POST'])
def register():
    try:
        # 确保数据库初始化并迁移到最新版本
        if not init_database():
            return jsonify({'error': '数据库初始化失败'}), 500
        
        data = request.get_json()
        if not data:
            return jsonify({'error': '无效的请求数据'}), 400
            
        username = data.get('username', '').strip()
        email = data.get('email', '').strip()
        password = data.get('password', '').strip()
        
        if not username or not email or not password:
            return jsonify({'error': '请填写所有字段'}), 400
        
        if len(password) < 6:
            return jsonify({'error': '密码至少需要6个字符'}), 400
        
        # 验证邮箱格式
        import re
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, email):
            return jsonify({'error': '邮箱格式不正确'}), 400
        
        conn = get_db_connection()
        
        # 检查用户名和邮箱是否已存在
        existing_user = conn.execute(
            'SELECT id FROM users WHERE username = ? OR email = ?',
            (username, email)
        ).fetchone()
        
        if existing_user:
            conn.close()
            return jsonify({'error': '用户名或邮箱已存在'}), 400
        
        # 创建新用户
        password_hash = generate_password_hash(password)
        cursor = conn.execute(
            'INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)',
            (username, email, password_hash)
        )
        user_id = cursor.lastrowid
        conn.commit()
        conn.close()
        
        # 自动登录
        session['user_id'] = user_id
        session['username'] = username
        
        print(f"用户注册成功: {username} (ID: {user_id})")
        return jsonify({'success': True, 'username': username})
        
    except Exception as e:
        print(f"注册错误: {str(e)}")
        # 如果是表不存在的错误，尝试重新初始化数据库
        if "no such table" in str(e):
            try:
                print("检测到表不存在，重新初始化数据库...")
                init_database()
                return register()  # 递归调用自己重试
            except Exception as init_error:
                print(f"数据库初始化失败: {init_error}")
                return jsonify({'error': f'数据库初始化失败: {str(init_error)}'}), 500
        return jsonify({'error': f'注册失败: {str(e)}'}), 500

@app.route('/api/login', methods=['POST'])
def login():
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': '无效的请求数据'}), 400
            
        username = data.get('username', '').strip()
        password = data.get('password', '').strip()
        
        if not username or not password:
            return jsonify({'error': '请填写用户名和密码'}), 400
        
        conn = get_db_connection()
        user = conn.execute(
            'SELECT id, username, password_hash FROM users WHERE username = ? OR email = ?',
            (username, username)
        ).fetchone()
        conn.close()
        
        if not user or not check_password_hash(user['password_hash'], password):
            return jsonify({'error': '用户名或密码错误'}), 400
        
        session['user_id'] = user['id']
        session['username'] = user['username']
        
        return jsonify({'success': True, 'username': user['username']})
    except Exception as e:
        print(f"登录错误: {str(e)}")
        return jsonify({'error': f'登录失败: {str(e)}'}), 500

@app.route('/api/logout', methods=['POST'])
def logout():
    session.clear()
    return jsonify({'success': True})

@app.route('/api/user', methods=['GET'])
def get_current_user():
    if 'user_id' in session:
        # 获取用户信息，包括角色
        conn = get_db_connection()
        user = conn.execute(
            'SELECT id, username, role FROM users WHERE id = ?',
            (session['user_id'],)
        ).fetchone()
        conn.close()
        
        # 如果找不到用户或角色为空，默认为普通用户
        role = user['role'] if user and 'role' in user.keys() and user['role'] else 'user'
        
        return jsonify({
            'logged_in': True,
            'user_id': session['user_id'],
            'username': session['username'],
            'role': role
        })
    return jsonify({'logged_in': False})

@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user_info(user_id):
    """获取指定用户的信息"""
    try:
        conn = get_db_connection()
        
        # 查询用户基本信息（不包含敏感信息如密码）
        user = conn.execute('''
            SELECT id, username, email, role, created_at 
            FROM users 
            WHERE id = ?
        ''', (user_id,)).fetchone()
        
        if not user:
            conn.close()
            return jsonify({'error': '用户不存在'}), 404
        
        # 查询用户的内容数量
        content_count = conn.execute('''
            SELECT COUNT(*) as count 
            FROM content 
            WHERE user_id = ?
        ''', (user_id,)).fetchone()['count']
        
        # 查询用户的评论数量
        comment_count = conn.execute('''
            SELECT COUNT(*) as count 
            FROM comments 
            WHERE user_id = ?
        ''', (user_id,)).fetchone()['count']
        
        # 判断是否是当前登录用户
        is_current_user = 'user_id' in session and session['user_id'] == user_id
        
        user_info = {
            'id': user['id'],
            'username': user['username'],
            'role': user['role'] or 'user',
            'created_at': user['created_at'],
            'content_count': content_count,
            'comment_count': comment_count,
            'is_current_user': is_current_user
        }
        
        # 如果是当前登录用户，添加邮箱信息
        if is_current_user:
            user_info['email'] = user['email']
        
        conn.close()
        return jsonify(user_info)
        
    except Exception as e:
        print(f"获取用户信息错误: {str(e)}")
        return jsonify({'error': f'获取用户信息失败: {str(e)}'}), 500

@app.route('/api/user/update', methods=['POST'])
@login_required
def update_user_info():
    """更新当前用户的信息"""
    try:
        data = request.get_json()
        user_id = session['user_id']
        
        # 验证请求
        if not data:
            return jsonify({'error': '无效的请求数据'}), 400
        
        conn = get_db_connection()
        
        # 更新邮箱
        if 'email' in data:
            new_email = data['email'].strip()
            
            # 验证邮箱格式
            import re
            email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
            if not re.match(email_pattern, new_email):
                conn.close()
                return jsonify({'error': '邮箱格式不正确'}), 400
            
            # 检查邮箱是否已被其他用户使用
            existing = conn.execute(
                'SELECT id FROM users WHERE email = ? AND id != ?', 
                (new_email, user_id)
            ).fetchone()
            
            if existing:
                conn.close()
                return jsonify({'error': '该邮箱已被其他用户使用'}), 400
            
            conn.execute('UPDATE users SET email = ? WHERE id = ?', (new_email, user_id))
        
        # 更新密码
        if 'password' in data:
            new_password = data['password'].strip()
            current_password = data.get('current_password', '').strip()
            
            # 验证密码长度
            if len(new_password) < 6:
                conn.close()
                return jsonify({'error': '新密码至少需要6个字符'}), 400
            
            # 验证当前密码
            user = conn.execute('SELECT password_hash FROM users WHERE id = ?', (user_id,)).fetchone()
            if not user or not check_password_hash(user['password_hash'], current_password):
                conn.close()
                return jsonify({'error': '当前密码不正确'}), 400
            
            # 更新密码
            password_hash = generate_password_hash(new_password)
            conn.execute('UPDATE users SET password_hash = ? WHERE id = ?', (password_hash, user_id))
        
        conn.commit()
        conn.close()
        
        return jsonify({'success': True, 'message': '用户信息已更新'})
        
    except Exception as e:
        print(f"更新用户信息错误: {str(e)}")
        return jsonify({'error': f'更新用户信息失败: {str(e)}'}), 500

@app.route('/api/content', methods=['POST'])
@login_required
def add_content():
    content_type = request.form.get('type')
    content_id = str(uuid.uuid4())
    user_id = session['user_id']
    
    content_data = {
        'id': content_id,
        'user_id': user_id,
        'type': content_type,
        'keywords': [],
        'summary': ""
    }
    
    if content_type == 'text':
        text_content = request.form.get('content', '')
        content_data['content'] = text_content
        content_data['keywords'] = extract_keywords(text_content)
        content_data['summary'] = generate_summary(text_content)
        
    elif content_type == 'file':
        if 'file' not in request.files:
            return jsonify({'error': '没有上传文件'}), 400
            
        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': '没有选择文件'}), 400
            
        if file and file.filename:
            filename = secure_filename(file.filename)
            original_filename = os.path.basename(file.filename)
            title_base = os.path.splitext(original_filename)[0]
            
            # 检查文件类型
            if not is_allowed_file(filename):
                return jsonify({'error': '不支持的文件类型'}), 400
            
            file_ext = os.path.splitext(filename)[1].lower()
            new_filename = f"{content_id}{file_ext}"
            file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
            file.save(file_path)
            
            file_type = get_file_type(filename)
            
            if file_type == 'image':
                # 处理图片文件
                extracted_text = extract_text_from_image(file_path)
                content_data['type'] = 'image'
                content_data['file_path'] = file_path
                content_data['title'] = title_base
                content_data['extracted_text'] = extracted_text
                content_data['keywords'] = extract_keywords(extracted_text)
                content_data['summary'] = generate_summary(extracted_text)
                
            elif file_type == 'markdown':
                # 处理Markdown文件
                markdown_data = process_markdown_file(file_path)
                content_data['type'] = 'markdown'
                content_data['file_path'] = file_path
                content_data['content'] = markdown_data['raw_content']
                content_data['html_content'] = markdown_data['html_content']
                content_data['raw_content'] = markdown_data['raw_content']
                md_title = markdown_data.get('title') if isinstance(markdown_data, dict) else None
                content_data['title'] = md_title if md_title and md_title != "Markdown文档" else title_base
                content_data['keywords'] = extract_keywords(markdown_data['plain_text'])
                content_data['summary'] = generate_summary(markdown_data['plain_text'])
                
            elif file_type == 'text_file':
                # 处理普通文本文件
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        text_content = f.read()
                except UnicodeDecodeError:
                    # 如果UTF-8解码失败，尝试其他编码
                    try:
                        with open(file_path, 'r', encoding='gbk') as f:
                            text_content = f.read()
                    except:
                        text_content = "无法读取文件内容"
                
                content_data['type'] = 'text_file'
                content_data['file_path'] = file_path
                content_data['content'] = text_content
                title_from_text = derive_title_from_text(text_content)
                content_data['title'] = title_from_text or title_base
                content_data['keywords'] = extract_keywords(text_content)
                content_data['summary'] = generate_summary(text_content)
            
            elif file_type == 'pdf':
                # 处理PDF文件
                pdf_data = process_pdf_file(file_path)
                content_data['type'] = 'pdf'
                content_data['file_path'] = file_path
                content_data['content'] = pdf_data['content']
                content_data['title'] = pdf_data['title'] or title_base
                content_data['page_count'] = pdf_data['page_count']
                content_data['keywords'] = extract_keywords(pdf_data['content'])
                content_data['summary'] = generate_summary(pdf_data['content'])
            
            else:
                return jsonify({'error': '不支持的文件类型'}), 400
    
    elif content_type == 'image':
        # 保持向后兼容性
        if 'file' not in request.files:
            return jsonify({'error': '没有上传文件'}), 400
            
        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': '没有选择文件'}), 400
            
        if file and file.filename:
            filename = secure_filename(file.filename)
            original_filename = os.path.basename(file.filename)
            title_base = os.path.splitext(original_filename)[0]
            file_ext = os.path.splitext(filename)[1].lower()
            new_filename = f"{content_id}{file_ext}"
            file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
            file.save(file_path)
            
            # 提取图片中的文本
            extracted_text = extract_text_from_image(file_path)
            
            content_data['file_path'] = file_path
            content_data['title'] = title_base
            content_data['extracted_text'] = extracted_text
            content_data['keywords'] = extract_keywords(extracted_text)
            content_data['summary'] = generate_summary(extracted_text)
            
    elif content_type == 'url':
        url = request.form.get('url', '')
        if not url:
            return jsonify({'error': '未提供URL'}), 400
            
        # 验证URL格式
        if not re.match(r'^https?://', url):
            url = 'http://' + url
            
        extracted_data = extract_from_url(url)
        content_data.update(extracted_data)
        content_data['keywords'] = extract_keywords(extracted_data['content'])
        content_data['summary'] = generate_summary(extracted_data['content'])
    
    # 保存到数据库
    try:
        conn = get_db_connection()
        conn.execute('''
            INSERT INTO content (id, user_id, type, content, file_path, url, title, extracted_text, keywords, summary, html_content, raw_content)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ''', (
            content_data['id'],
            content_data['user_id'],
            content_data['type'],
            content_data.get('content'),
            content_data.get('file_path'),
            content_data.get('url'),
            content_data.get('title'),
            content_data.get('extracted_text'),
            ','.join(content_data['keywords']),
            content_data['summary'],
            content_data.get('html_content'),
            content_data.get('raw_content')
        ))
        conn.commit()
        conn.close()
        print(f"成功保存内容到数据库: {content_data['id']}")
    except Exception as e:
        print(f"数据库保存失败: {e}")
        print(f"内容数据: {content_data}")
        return jsonify({'error': f'保存失败: {str(e)}'}), 500
    
    return jsonify({
        'id': content_id,
        'keywords': content_data['keywords'],
        'summary': content_data['summary']
    })

@app.route('/api/search', methods=['GET'])
def search_content():
    keyword = request.args.get('keyword', '')
    if not keyword:
        return jsonify({'error': '请提供搜索关键词'}), 400
    
    conn = get_db_connection()
    
    # 搜索内容
    content_results = conn.execute('''
        SELECT c.*, u.username,
               (SELECT COUNT(*) FROM comments cm WHERE cm.content_id = c.id) AS comment_count
        FROM content c 
        JOIN users u ON c.user_id = u.id 
        WHERE c.keywords LIKE ? OR c.content LIKE ? OR c.extracted_text LIKE ? OR c.title LIKE ?
        ORDER BY c.created_at DESC
    ''', (f'%{keyword}%', f'%{keyword}%', f'%{keyword}%', f'%{keyword}%')).fetchall()
    
    # 搜索评论
    comment_results = conn.execute('''
        SELECT cm.*, u.username, c.title as content_title, c.id as content_id
        FROM comments cm
        JOIN users u ON cm.user_id = u.id
        JOIN content c ON cm.content_id = c.id
        WHERE cm.comment LIKE ?
        ORDER BY cm.created_at DESC
    ''', (f'%{keyword}%',)).fetchall()
    
    conn.close()
    
    # 转换内容结果
    content_list = []
    for row in content_results:
        item = dict(row)
        item['keywords'] = item['keywords'].split(',') if item['keywords'] else []
        item['result_type'] = 'content'  # 标记结果类型为内容
        content_list.append(item)
    
    # 转换评论结果
    comment_list = []
    for row in comment_results:
        item = dict(row)
        item['result_type'] = 'comment'  # 标记结果类型为评论
        comment_list.append(item)
    
    # 合并结果
    all_results = {
        'content': content_list,
        'comments': comment_list
    }
    
    return jsonify({'results': all_results})

@app.route('/api/content', methods=['GET'])
def get_all_content():
    try:
        conn = get_db_connection()
        
        results = conn.execute('''
            SELECT c.*, u.username,
                   (SELECT COUNT(*) FROM comments cm WHERE cm.content_id = c.id) AS comment_count
            FROM content c 
            JOIN users u ON c.user_id = u.id 
            ORDER BY c.created_at DESC
        ''').fetchall()
        
        conn.close()
        
        # 转换结果
        content_list = []
        for row in results:
            item = dict(row)
            # 确保keywords是列表格式
            if item['keywords']:
                item['keywords'] = [k.strip() for k in item['keywords'].split(',') if k.strip()]
            else:
                item['keywords'] = []
            content_list.append(item)
        
        return jsonify({'content': content_list})
        
    except Exception as e:
        print(f"获取内容列表错误: {str(e)}")
        return jsonify({'error': f'获取内容失败: {str(e)}'}), 500

@app.route('/api/content/<content_id>', methods=['GET'])
def get_single_content(content_id):
    """获取单个内容的详细信息"""
    try:
        conn = get_db_connection()
        
        # 获取内容详情
        content = conn.execute('''
            SELECT c.*, u.username,
                   (SELECT COUNT(*) FROM comments cm WHERE cm.content_id = c.id) AS comment_count
            FROM content c 
            JOIN users u ON c.user_id = u.id 
            WHERE c.id = ?
        ''', (content_id,)).fetchone()
        
        if not content:
            conn.close()
            return jsonify({'error': '内容不存在'}), 404
        
        # 转换为字典
        content_data = dict(content)
        
        # 处理关键词
        if content_data['keywords']:
            content_data['keywords'] = [k.strip() for k in content_data['keywords'].split(',') if k.strip()]
        else:
            content_data['keywords'] = []
        
        conn.close()
        return jsonify(content_data)
        
    except Exception as e:
        print(f"获取内容详情错误: {str(e)}")
        return jsonify({'error': f'获取内容失败: {str(e)}'}), 500

# ========== 下载 API：/api/download/<content_id> ==========
def _sanitize_filename(name: Optional[Any], fallback: str = 'content') -> str:
    s = re.sub(r'[\\/:*?"<>|]+', '_', str(name) if name is not None else '')
    s = re.sub(r'\s+', ' ', s).strip()
    return s or fallback

def _smart_title_for_download(row: dict[str, Any]) -> str:
    try:
        title = (row.get('title') or '').strip()
        if title:
            return title
        if (row.get('type') or '').lower() == 'markdown' and row.get('raw_content'):
            for line in row['raw_content'].splitlines():
                m = re.match(r'^\s{0,3}#{1,6}\s+(.+?)\s*$', line)
                if m:
                    return m.group(1)
        if row.get('html_content'):
            try:
                soup = BeautifulSoup(row['html_content'], 'html.parser')
                h = soup.find(['h1','h2','h3'])
                if h and h.get_text(strip=True):
                    return h.get_text(strip=True)
            except Exception:
                pass
        if row.get('url'):
            try:
                from urllib.parse import urlparse
                u = urlparse(row['url'])
                last = os.path.basename(u.path) or u.netloc
                if last:
                    return last
            except Exception:
                pass
        kw = row.get('keywords')
        if isinstance(kw, str):
            arr = [k.strip() for k in kw.split(',') if k.strip()]
            if arr:
                return arr[0]
        txt = (row.get('content') or row.get('extracted_text') or row.get('summary') or '').strip()
        if txt:
            return re.sub(r'\s+', ' ', txt)[:50]
    except Exception:
        pass
    return f"content_{row.get('id','')}"

@app.route('/api/content/<content_id>/delete', methods=['POST'])
@login_required
def delete_content(content_id):
    """删除内容"""
    try:
        user_id = session['user_id']
        is_admin = session.get('is_admin', False)
        
        conn = get_db_connection()
        
        # 检查内容是否存在以及是否属于当前用户
        content = conn.execute(
            'SELECT * FROM content WHERE id = ?', 
            (content_id,)
        ).fetchone()
        
        if not content:
            conn.close()
            return jsonify({'error': '内容不存在'}), 404
        
        # 只有内容所有者或管理员可以删除
        if content['user_id'] != user_id and not is_admin:
            conn.close()
            return jsonify({'error': '没有权限删除此内容'}), 403
        
        # 删除相关评论
        conn.execute('DELETE FROM comments WHERE content_id = ?', (content_id,))
        
        # 删除内容
        conn.execute('DELETE FROM content WHERE id = ?', (content_id,))
        
        # 如果有关联文件，删除文件
        if content['file_path']:
            try:
                file_path = content['file_path']
                if os.path.exists(file_path):
                    os.remove(file_path)
            except Exception as e:
                print(f"删除文件错误: {str(e)}")
        
        conn.commit()
        conn.close()
        
        return jsonify({'success': True, 'message': '内容已成功删除'})
        
    except Exception as e:
        print(f"删除内容错误: {str(e)}")
        return jsonify({'error': f'删除内容失败: {str(e)}'}), 500

@app.route('/api/download/<content_id>', methods=['GET'])
def download_content(content_id):
    try:
        conn = get_db_connection()
        row = conn.execute('''
            SELECT *
            FROM content
            WHERE id = ?
        ''', (content_id,)).fetchone()
        conn.close()
        if not row:
            return jsonify({'error': '内容不存在'}), 404

        data = dict(row)
        title_base = _sanitize_filename(_smart_title_for_download(data), 'content')

        file_path = data.get('file_path')
        if file_path:
            abs_path = file_path if os.path.isabs(file_path) else os.path.join(os.getcwd(), file_path)
            if os.path.exists(abs_path):
                ext = os.path.splitext(abs_path)[1] or ''
                filename = title_base + ext
                return send_file(abs_path, as_attachment=True, download_name=filename)

        # 无物理文件时，导出文本
        ctype = (data.get('type') or '').lower()
        text = ''
        mime = 'text/plain; charset=utf-8'
        filename = title_base + '.txt'

        if ctype == 'markdown':
            text = (data.get('content') or data.get('raw_content') or '') or ''
            mime = 'text/markdown; charset=utf-8'
            filename = title_base + '.md'
        elif ctype == 'url':
            head = f"Source: {data.get('url','')}\n\n" if data.get('url') else ''
            text = head + (data.get('content') or '')
        elif ctype == 'image':
            text = data.get('extracted_text') or ''
        else:
            text = data.get('content') or data.get('extracted_text') or ''

        from flask import Response
        resp = Response(text.encode('utf-8'), mimetype=mime)
        try:
            resp.headers['Content-Disposition'] = f"attachment; filename*=UTF-8''{filename}"
        except Exception:
            resp.headers['Content-Disposition'] = f'attachment; filename="{filename}"'
        return resp
    except Exception as e:
        print(f"下载错误: {e}")
        return jsonify({'error': f'下载失败: {str(e)}'}), 500

# ========== 下载 API End ==========

@app.route('/api/content/<content_id>/keywords', methods=['POST'])
@login_required
def update_content_keywords(content_id):
    """更新指定内容的关键词（仅作者可编辑）"""
    try:
        data = request.get_json(silent=True) or {}
        kw_list = data.get('keywords', [])
        if not isinstance(kw_list, list):
            return jsonify({'error': '参数格式错误，keywords 应为数组'}), 400

        # 清洗与去重（保持原顺序，大小写不敏感去重）
        cleaned = []
        for k in kw_list:
            if isinstance(k, str):
                s = k.strip()
                if s:
                    cleaned.append(s)
        seen = set()
        unique = []
        for s in cleaned:
            key = s.lower()
            if key not in seen:
                seen.add(key)
                unique.append(s)

        user_id = session.get('user_id')
        conn = get_db_connection()

        # 检查内容存在与归属
        row = conn.execute('SELECT user_id FROM content WHERE id = ?', (content_id,)).fetchone()
        if not row:
            conn.close()
            return jsonify({'error': '内容不存在'}), 404
        if row['user_id'] != user_id:
            conn.close()
            return jsonify({'error': '无权限编辑该内容'}), 403

        # 更新关键词（以逗号分隔）
        keywords_str = ','.join(unique) if unique else ""
        conn.execute('UPDATE content SET keywords = ? WHERE id = ?', (keywords_str, content_id))
        conn.commit()
        conn.close()

        return jsonify({'success': True, 'keywords': unique})
    except Exception as e:
        print(f"更新关键词错误: {str(e)}")
        return jsonify({'error': f'更新关键词失败: {str(e)}'}), 500

@app.route('/api/content/<content_id>/comments', methods=['GET'])
def get_comments(content_id):
    try:
        conn = get_db_connection()
        
        comments = conn.execute('''
            SELECT c.*, u.username 
            FROM comments c 
            JOIN users u ON c.user_id = u.id 
            WHERE c.content_id = ? 
            ORDER BY c.created_at ASC
        ''', (content_id,)).fetchall()
        
        conn.close()
        
        return jsonify({'comments': [dict(row) for row in comments]})
    except Exception as e:
        print(f"获取评论错误: {str(e)}")
        return jsonify({'error': f'获取评论失败: {str(e)}'}), 500

@app.route('/api/preview/<content_id>', methods=['GET'])
def preview_content(content_id):
    """预览内容，特别是PDF文件"""
    try:
        conn = get_db_connection()
        content = conn.execute('''
            SELECT * FROM content WHERE id = ?
        ''', (content_id,)).fetchone()
        conn.close()
        
        if not content:
            return jsonify({'error': '内容不存在'}), 404
        
        file_path = content['file_path']
        if not file_path or not os.path.exists(file_path):
            return jsonify({'error': '文件不存在'}), 404
        
        # 对于PDF文件，直接返回文件内容
        if content['type'] == 'pdf':
            # 检查是否请求特定页面的图像预览
            page_num = request.args.get('page', None)
            if page_num is not None:
                try:
                    # 使用PyMuPDF生成特定页面的图像预览
                    import fitz
                    page_num = int(page_num)
                    doc = fitz.open(file_path)
                    
                    if 0 <= page_num < len(doc):
                        page = doc[page_num]
                        # 渲染页面为图像
                        pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))  # 2x缩放以提高清晰度
                        img_data = pix.tobytes("png")
                        
                        doc.close()
                        return Response(img_data, mimetype='image/png')
                    else:
                        doc.close()
                        return jsonify({'error': '页面不存在'}), 404
                except Exception as e:
                    print(f"生成PDF页面预览错误: {e}")
                    # 如果生成预览失败，回退到直接返回PDF
            
            # 直接返回PDF文件
            return send_file(
                file_path,
                mimetype='application/pdf',
                as_attachment=False,
                download_name=f"{_sanitize_filename(content['title'], 'document')}.pdf"
            )
        
        # 对于其他类型的文件，返回错误
        return jsonify({'error': '不支持预览此类型的文件'}), 400
        
    except Exception as e:
        print(f"预览内容错误: {str(e)}")
        return jsonify({'error': f'预览失败: {str(e)}'}), 500

@app.route('/api/pdf/search/<content_id>', methods=['GET'])
def search_pdf(content_id):
    """在PDF文件中搜索文本"""
    try:
        search_term = request.args.get('q', '')
        if not search_term:
            return jsonify({'error': '搜索词不能为空'}), 400
            
        conn = get_db_connection()
        content = conn.execute('''
            SELECT * FROM content WHERE id = ? AND type = 'pdf'
        ''', (content_id,)).fetchone()
        conn.close()
        
        if not content:
            return jsonify({'error': '未找到PDF文件'}), 404
            
        file_path = content['file_path']
        if not file_path or not os.path.exists(file_path):
            return jsonify({'error': '文件不存在'}), 404
            
        # 使用PyMuPDF在PDF中搜索
        import fitz
        doc = fitz.open(file_path)
        results = []
        
        for page_num in range(len(doc)):
            page = doc[page_num]
            # 搜索当前页面
            # PyMuPDF API可能因版本不同而变化，这里使用兼容性更好的方法
            try:
                # 新版API
                text_instances = page.search_for(search_term)
            except AttributeError:
                # 旧版API
                text_instances = page.searchFor(search_term)
            
            if text_instances:
                # 获取页面文本
                try:
                    # 新版API
                    page_text = page.get_text()
                except AttributeError:
                    # 旧版API
                    page_text = page.getText()
                
                for inst in text_instances:
                    # 获取匹配位置的上下文
                    # 将矩形区域转换为文本位置（这是一个近似值）
                    rect = inst
                    
                    # 提取匹配位置周围的文本
                    # 由于PyMuPDF不直接提供文本位置，我们使用页面文本和搜索词来估算
                    search_term_lower = search_term.lower()
                    page_text_lower = page_text.lower()
                    
                    # 查找所有匹配项
                    start_pos = 0
                    while True:
                        pos = page_text_lower.find(search_term_lower, start_pos)
                        if pos == -1:
                            break
                            
                        # 获取上下文
                        context_start = max(0, pos - 50)
                        context_end = min(len(page_text), pos + len(search_term) + 50)
                        context = page_text[context_start:context_end]
                        
                        results.append({
                            'page': page_num + 1,  # 页码从1开始
                            'context': context,
                            'rect': [rect.x0, rect.y0, rect.x1, rect.y1]  # 矩形坐标
                        })
                        
                        start_pos = pos + len(search_term)
        
        doc.close()
        
        return jsonify({
            'query': search_term,
            'total': len(results),
            'results': results
        })
        
    except Exception as e:
        print(f"PDF搜索错误: {str(e)}")
        return jsonify({'error': f'搜索失败: {str(e)}'}), 500

@app.route('/api/content/<content_id>/comments', methods=['POST'])
@login_required
def add_comment(content_id):
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': '无效的请求数据'}), 400
            
        comment_text = data.get('comment', '').strip()
        parent_id = data.get('parent_id')  # 获取父评论ID
        
        if not comment_text:
            return jsonify({'error': '评论内容不能为空'}), 400
        
        user_id = session['user_id']
        
        conn = get_db_connection()
        
        # 检查内容是否存在
        content = conn.execute('SELECT id FROM content WHERE id = ?', (content_id,)).fetchone()
        if not content:
            conn.close()
            return jsonify({'error': '内容不存在'}), 404
        
        # 如果有父评论ID，检查父评论是否存在
        if parent_id:
            parent_comment = conn.execute('SELECT id FROM comments WHERE id = ?', (parent_id,)).fetchone()
            if not parent_comment:
                conn.close()
                return jsonify({'error': '回复的评论不存在'}), 404
        
        # 添加评论，包含父评论ID
        cursor = conn.execute('''
            INSERT INTO comments (content_id, user_id, comment, parent_id)
            VALUES (?, ?, ?, ?)
        ''', (content_id, user_id, comment_text, parent_id))
        
        comment_id = cursor.lastrowid
        conn.commit()
        conn.close()
        
        return jsonify({'success': True, 'comment_id': comment_id})
    except Exception as e:
        print(f"添加评论错误: {str(e)}")
        return jsonify({'error': f'添加评论失败: {str(e)}'}), 500

@app.route('/api/messages', methods=['POST'])
@login_required
def send_message():
    """发送私信"""
    try:
        data = request.get_json()
        recipient_id = data.get('recipient_id')
        subject = data.get('subject', '').strip()
        content = data.get('content', '').strip()
        
        if not recipient_id or not subject or not content:
            return jsonify({'error': '请填写所有必需字段'}), 400
        
        sender_id = session['user_id']
        
        # 检查收件人是否存在
        conn = get_db_connection()
        recipient = conn.execute('SELECT id FROM users WHERE id = ?', (recipient_id,)).fetchone()
        if not recipient:
            conn.close()
            return jsonify({'error': '收件人不存在'}), 404
        
        # 不能给自己发私信
        if sender_id == int(recipient_id):
            conn.close()
            return jsonify({'error': '不能给自己发送私信'}), 400
        
        # 插入私信记录
        conn.execute('''
            INSERT INTO messages (sender_id, recipient_id, subject, content)
            VALUES (?, ?, ?, ?)
        ''', (sender_id, recipient_id, subject, content))
        
        conn.commit()
        conn.close()
        
        return jsonify({'success': True, 'message': '私信发送成功'})
        
    except Exception as e:
        print(f"发送私信错误: {str(e)}")
        return jsonify({'error': f'发送私信失败: {str(e)}'}), 500

@app.route('/api/messages', methods=['GET'])
@login_required
def get_messages():
    """获取当前用户的私信列表"""
    try:
        user_id = session['user_id']
        message_type = request.args.get('type', 'received')  # received 或 sent
        
        conn = get_db_connection()
        
        if message_type == 'sent':
            # 获取发送的私信
            messages = conn.execute('''
                SELECT m.*, u.username as recipient_username
                FROM messages m
                JOIN users u ON m.recipient_id = u.id
                WHERE m.sender_id = ?
                ORDER BY m.created_at DESC
            ''', (user_id,)).fetchall()
        else:
            # 获取接收的私信
            messages = conn.execute('''
                SELECT m.*, u.username as sender_username
                FROM messages m
                JOIN users u ON m.sender_id = u.id
                WHERE m.recipient_id = ?
                ORDER BY m.created_at DESC
            ''', (user_id,)).fetchall()
        
        conn.close()
        
        return jsonify({
            'messages': [dict(row) for row in messages],
            'type': message_type
        })
        
    except Exception as e:
        print(f"获取私信列表错误: {str(e)}")
        return jsonify({'error': f'获取私信失败: {str(e)}'}), 500

# 未读私信数量
@app.route('/api/messages/unread_count', methods=['GET'])
@login_required
def get_unread_messages_count():
    try:
        user_id = session['user_id']
        conn = get_db_connection()
        row = conn.execute('''
            SELECT COUNT(*) AS count
            FROM messages
            WHERE recipient_id = ? AND (is_read IS NULL OR is_read = 0)
        ''', (user_id,)).fetchone()
        conn.close()
        
        # 简化条件判断
        count = 0
        if row and hasattr(row, 'keys') and 'count' in row.keys():
            count = row['count']
        
        return jsonify({'unread': count})
    except Exception as e:
        print(f"获取未读私信数量错误: {str(e)}")
        return jsonify({'error': f'获取未读数失败: {str(e)}'}), 500

@app.route('/api/messages/<int:message_id>/read', methods=['POST'])
@login_required
def mark_message_read(message_id):
    """标记私信为已读"""
    try:
        user_id = session['user_id']
        
        conn = get_db_connection()
        
        # 检查私信是否存在且属于当前用户
        message = conn.execute('''
            SELECT id FROM messages 
            WHERE id = ? AND recipient_id = ?
        ''', (message_id, user_id)).fetchone()
        
        if not message:
            conn.close()
            return jsonify({'error': '私信不存在或无权限'}), 404
        
        # 标记为已读
        conn.execute('''
            UPDATE messages 
            SET is_read = TRUE, read_at = CURRENT_TIMESTAMP 
            WHERE id = ?
        ''', (message_id,))
        
        conn.commit()
        conn.close()
        
        return jsonify({'success': True})
        
    except Exception as e:
        print(f"标记私信已读错误: {str(e)}")
        return jsonify({'error': f'操作失败: {str(e)}'}), 500

# ========== 批注 API ==========
@app.route('/api/content/<content_id>/annotations', methods=['GET'])
@login_required
def list_annotations(content_id):
    try:
        conn = get_db_connection()
        c = conn.execute('SELECT id, type FROM content WHERE id = ?', (content_id,)).fetchone()
        if not c:
            conn.close()
            return jsonify({'error': '内容不存在'}), 404
        user_id = session.get('user_id')
        rows = conn.execute('''
            SELECT a.*, u.username
            FROM annotations a
            JOIN users u ON a.user_id = u.id
            WHERE a.content_id = ?
              AND (a.is_private = 0 OR a.user_id = ?)
            ORDER BY a.created_at ASC
        ''', (content_id, user_id)).fetchall()
        conn.close()
        return jsonify({'annotations': [dict(r) for r in rows]})
    except Exception as e:
        print(f"获取批注错误: {e}")
        return jsonify({'error': f'获取批注失败: {str(e)}'}), 500

@app.route('/api/content/<content_id>/annotations', methods=['POST'])
@login_required
def create_annotation(content_id):
    try:
        data = request.get_json(silent=True) or {}
        note = (data.get('note') or '').strip()
        anchor = data.get('anchor')
        is_private = bool(data.get('is_private', False))
        if not note or not anchor:
            return jsonify({'error': '缺少批注内容或锚点'}), 400

        # 归一化并裁剪锚点，确保包含 version=2 与字段长度控制（兼容旧数据）
        try:
            if isinstance(anchor, str):
                anchor = json.loads(anchor)
        except Exception:
            return jsonify({'error': '锚点格式错误'}), 400
        if not isinstance(anchor, dict):
            return jsonify({'error': '锚点格式错误'}), 400

        # 字段长度限制，避免超长数据
        def _limit_str(s, n=200):
            try:
                s = str(s or '')
                return s[:n]
            except Exception:
                return ''

        anchor['method'] = anchor.get('method') or 'text-quote'
        if 'quote' in anchor:
            anchor['quote'] = _limit_str(anchor.get('quote'), 200)
        if 'context_before' in anchor:
            anchor['context_before'] = _limit_str(anchor.get('context_before'), 200)
        if 'context_after' in anchor:
            anchor['context_after'] = _limit_str(anchor.get('context_after'), 200)
        # 版本标记
        anchor['version'] = 2

        import json as _json
        user_id = session['user_id']
        conn = get_db_connection()

        row = conn.execute('SELECT type FROM content WHERE id = ?', (content_id,)).fetchone()
        if not row:
            conn.close()
            return jsonify({'error': '内容不存在'}), 404

        ctype = row['type']
        cursor = conn.execute('''
            INSERT INTO annotations (content_id, user_id, type, anchor_json, note, is_private)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (content_id, user_id, ctype, _json.dumps(anchor, ensure_ascii=False), note, 1 if is_private else 0))
        ann_id = cursor.lastrowid
        conn.commit()

        ann = conn.execute('''
            SELECT a.*, u.username FROM annotations a
            JOIN users u ON a.user_id = u.id
            WHERE a.id = ?
        ''', (ann_id,)).fetchone()
        conn.close()
        return jsonify({'success': True, 'annotation': dict(ann)})
    except Exception as e:
        print(f"创建批注错误: {e}")
        return jsonify({'error': f'创建批注失败: {str(e)}'}), 500

@app.route('/api/annotations/<int:annotation_id>', methods=['PUT'])
@login_required
def update_annotation(annotation_id):
    try:
        user_id = session['user_id']
        data = request.get_json(silent=True) or {}
        note = data.get('note')
        is_private = data.get('is_private')
        new_anchor = data.get('anchor', None)

        conn = get_db_connection()
        row = conn.execute('SELECT user_id FROM annotations WHERE id = ?', (annotation_id,)).fetchone()
        if not row:
            conn.close()
            return jsonify({'error': '批注不存在'}), 404

        is_admin = session.get('is_admin', False)
        if row['user_id'] != user_id and not is_admin:
            conn.close()
            return jsonify({'error': '无权限操作'}), 403

        sets, params = [], []
        if isinstance(note, str):
            sets.append('note = ?')
            params.append(note.strip())
        if is_private is not None:
            sets.append('is_private = ?')
            params.append(1 if bool(is_private) else 0)

        # 可选更新锚点
        if new_anchor is not None:
            try:
                if isinstance(new_anchor, str):
                    new_anchor = json.loads(new_anchor)
            except Exception:
                conn.close()
                return jsonify({'error': '锚点格式错误'}), 400
            if not isinstance(new_anchor, dict):
                conn.close()
                return jsonify({'error': '锚点格式错误'}), 400

            def _limit_str(s, n=200):
                try:
                    s = str(s or '')
                    return s[:n]
                except Exception:
                    return ''
            new_anchor['method'] = new_anchor.get('method') or 'text-quote'
            if 'quote' in new_anchor:
                new_anchor['quote'] = _limit_str(new_anchor.get('quote'), 200)
            if 'context_before' in new_anchor:
                new_anchor['context_before'] = _limit_str(new_anchor.get('context_before'), 200)
            if 'context_after' in new_anchor:
                new_anchor['context_after'] = _limit_str(new_anchor.get('context_after'), 200)
            new_anchor['version'] = 2

            sets.append('anchor_json = ?')
            import json as _json
            params.append(_json.dumps(new_anchor, ensure_ascii=False))

        if not sets:
            conn.close()
            return jsonify({'error': '没有可更新的字段'}), 400

        sets.append('updated_at = CURRENT_TIMESTAMP')
        sql = 'UPDATE annotations SET ' + ', '.join(sets) + ' WHERE id = ?'
        params.append(annotation_id)
        conn.execute(sql, tuple(params))
        conn.commit()

        ann = conn.execute('''
            SELECT a.*, u.username FROM annotations a
            JOIN users u ON a.user_id = u.id
            WHERE a.id = ?
        ''', (annotation_id,)).fetchone()
        conn.close()
        return jsonify({'success': True, 'annotation': dict(ann)})
    except Exception as e:
        print(f"更新批注错误: {e}")
        return jsonify({'error': f'更新批注失败: {str(e)}'}), 500

@app.route('/api/annotations/<int:annotation_id>', methods=['DELETE'])
@login_required
def delete_annotation(annotation_id):
    try:
        user_id = session['user_id']
        conn = get_db_connection()
        row = conn.execute('SELECT user_id FROM annotations WHERE id = ?', (annotation_id,)).fetchone()
        if not row:
            conn.close()
            return jsonify({'error': '批注不存在'}), 404

        is_admin = session.get('is_admin', False)
        if row['user_id'] != user_id and not is_admin:
            conn.close()
            return jsonify({'error': '无权限操作'}), 403

        conn.execute('DELETE FROM annotations WHERE id = ?', (annotation_id,))
        conn.commit()
        conn.close()
        return jsonify({'success': True})
    except Exception as e:
        print(f"删除批注错误: {e}")
        return jsonify({'error': f'删除批注失败: {str(e)}'}), 500

# ========= 批注 API End ==========

def check_database_status():
    """检查数据库状态并确保其正确初始化"""
    from db_migration import init_database, verify_database_integrity, repair_database, get_current_db_version, DB_VERSIONS
    
    print("正在检查数据库状态...")
    
    # 获取当前和最新版本
    current_version = get_current_db_version()
    latest_version = DB_VERSIONS[-1]['version']
    print(f"当前数据库版本: {current_version}")
    print(f"最新数据库版本: {latest_version}")
    
    # 检查数据库完整性
    integrity_check = verify_database_integrity()
    
    # 如果版本不匹配或完整性检查失败，尝试迁移或修复
    if isinstance(current_version, int) and isinstance(latest_version, int) and current_version < latest_version:
        print("数据库需要升级，正在执行迁移...")
        if not init_database():
            print("数据库迁移失败，尝试修复...")
            if not repair_database():
                return False
    elif not integrity_check:
        print("数据库完整性检查失败，尝试修复...")
        if not repair_database():
            return False
    
    # 最后再次验证数据库完整性
    if not verify_database_integrity():
        print("数据库修复后仍然存在问题，应用可能无法正常工作")
        return False
    
    print("数据库检查完成，状态正常")
    return True

if __name__ == '__main__':
    print("正在初始化数据库...")
    if check_database_status():
        print("数据库初始化完成，启动应用...")
        app.config['TEMPLATES_AUTO_RELOAD'] = True
        app.run(debug=True, host='0.0.0.0', port=5000)
    else:
        print("数据库初始化失败，应用无法启动")
